package com.async.biz.impl;

import com.async.biz.AsyncBizService;
import com.async.config.AsyncConverter;
import com.async.config.AsyncProxyContext;
import com.async.constant.AsyncConstant;
import com.async.domain.entity.AsyncLog;
import com.async.domain.entity.AsyncReq;
import com.async.domain.service.AsyncLogService;
import com.async.domain.service.AsyncReqService;
import com.async.dto.AsyncExecDto;
import com.async.dto.ProxyMethodDto;
import com.async.enums.AsyncTypeEnum;
import com.async.enums.ExecStatusEnum;
import com.async.util.JacksonUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Type;

import java.util.Objects;

/**
 * <p>
 * 消费异步任务实现类
 * </p>
 * @author yang
 * @date 2024/5/17
 */
@Slf4j
@Component
public class AsyncBizServiceImpl implements AsyncBizService {

    @Autowired
    private AsyncReqService asyncReqService;

    @Autowired
    private AsyncLogService asyncLogService;

    @Autowired
    private AsyncConverter asyncConverter;

    @Autowired
    private AsyncProxyContext asyncProxyContext;

    /**
     * 执行成功是否删除：默认是
     */
    @Value("${async.exec.deleted:true}")
    private boolean deleted;

    /**
     * 最大重试执行次数：默认5次
     */
    @Value("${async.exec.count:5}")
    private int execCount;

    @Override
    public boolean invoke(AsyncReq asyncReq) {
        return this.invoke(asyncConverter.toAsyncExecDto.apply(asyncReq));
    }

    @Override
    public boolean invoke(AsyncExecDto asyncExecDto) {
        if (Objects.isNull(asyncExecDto)){
            return true;
        }
        // 标记说明当前方法正在异步，避免死循环
        AsyncConstant.PUBLISH_EVENT.set(Boolean.TRUE);
        ProxyMethodDto proxyMethodDto = asyncProxyContext.getAsyncMethod(asyncExecDto.getSign());
        if (Objects.isNull(proxyMethodDto)){
            log.error("异步方法不存在，请检查配置");
            return true;
        }
        // 判断是否是补偿方法
        if (Objects.isNull(asyncExecDto.getId())){
            return this.execute(asyncExecDto,proxyMethodDto);
        }else {
            return this.recoupExecute(asyncExecDto,proxyMethodDto);
        }

    }

    private boolean recoupExecute(AsyncExecDto asyncExecDto, ProxyMethodDto proxyMethodDto) {
        AsyncReq asyncReq = asyncReqService.getById(asyncExecDto.getId());
        if (Objects.isNull(asyncReq)){
            log.error("异步方法不存在:{}",asyncExecDto.getId());
            return true;
        }
        try {
            this.invokeMethod(proxyMethodDto, asyncExecDto);
            this.updateAsyncReq(asyncReq);
            return true;
        }catch (Exception e){
            if (asyncReq.getExecCount() + 1 > execCount){
                log.error("异步执行方法失败超过{}次：{}", execCount, asyncExecDto, e);
                return false;
            }
            asyncReqService.updateStatusAndExecCount(asyncReq.getId(), ExecStatusEnum.ERROR.getStatus());
            // 更新执行失败日志
            this.saveAsyncLog(asyncReq, e);
            return false;
        }
    }

    /**
     * 执行失败更新执行次数且记录失败日志
     * @param asyncReq
     * @param e
     */
    private void saveAsyncLog(AsyncReq asyncReq, Exception e) {
        // 保存执行失败日志
        AsyncLog asyncLog = new AsyncLog();
        asyncLog.setAsyncId(asyncReq.getId());
        asyncLog.setErrorData(ExceptionUtils.getStackTrace(e));
        asyncLogService.save(asyncLog);
        log.info("处理失败后保存失败日志成功：{}", asyncReq);
    }

    private void updateAsyncReq(AsyncReq asyncReq) {
        if (deleted){
            asyncReqService.delete(asyncReq.getId());
        }else {
            asyncReqService.updateStatusAndExecCount(asyncReq.getId(), ExecStatusEnum.SUCCESS.getStatus());
        }
        // 删除日志
        asyncLogService.delete(asyncReq.getId());
    }

    private boolean execute(AsyncExecDto asyncExecDto, ProxyMethodDto proxyMethodDto) {
        try {
            this.invokeMethod(proxyMethodDto, asyncExecDto);
            return true;
        }catch (Exception e){
            log.error("异步执行方法失败：{}", asyncExecDto, e);
            // 根据类型是否保存到数据库
            saveAsyncReqByType(asyncExecDto);
            return false;
        }
    }

    private void saveAsyncReqByType(AsyncExecDto asyncExecDto) {
        String asyncType = asyncExecDto.getAsyncType();
        if (AsyncTypeEnum.ASYNC.name().equals(asyncType) ||
            AsyncTypeEnum.THREAD.name().equals(asyncType)){
            log.error("【{}】执行失败：{}", AsyncTypeEnum.getDesc(asyncExecDto.getAsyncType()), asyncExecDto);
        }else {
            AsyncReq asyncReq = asyncConverter.toAsyncReq.apply(asyncExecDto);
            asyncReq.setExecStatus(ExecStatusEnum.ERROR.getStatus());
            asyncReqService.save(asyncReq);
            log.info("处理失败后保存数据库成功：{}", asyncReq);
        }
    }


    /**
     * 反射执行异步方法
     *
     * @param proxyMethodDto
     * @param asyncExecDto
     */
    private void invokeMethod(ProxyMethodDto proxyMethodDto, AsyncExecDto asyncExecDto) {
        log.info("异步执行方法开始：{}", asyncExecDto);
        // 获取参数类型
        Object[] paramTypes = this.getParamType(proxyMethodDto.getMethod(), asyncExecDto.getParamJson());
        // 执行方法
        ReflectionUtils.invokeMethod(proxyMethodDto.getMethod(), proxyMethodDto.getBean(), paramTypes);
        log.info("异步执行方法成功：{}", asyncExecDto);
    }


    /**
     * 获取方法参数
     *
     * @param method
     * @param data
     * @return
     */
    private Object[] getParamType(Method method, String data) {
        Type[] types = method.getGenericParameterTypes();
        if (types.length == 0) {
            return null;
        }
        return JacksonUtil.toObjects(data, types);
    }


    //    @Override
//    public void batchExecute(List<AsyncReq> asyncReqs,boolean isComp) {
//        // 批量查询出数据存在的失败重试方法
//        AsyncConstant.PUBLISH_EVENT.set(Boolean.TRUE);
//        List<Long> executeFailedIds = null;
//        // 循环执行
//        for (AsyncReq asyncReq : asyncReqs) {
//            AsyncExecDto asyncExecDto = asyncConverter.toAsyncExecDto.apply(asyncReq);
//            ProxyMethodDto proxyMethodDto = asyncProxyContext.getAsyncMethod(asyncExecDto.getSign());
//            if (Objects.isNull(proxyMethodDto)){
//                log.error("异步方法不存在，请检查配置：{}",asyncExecDto.getSign());
//                continue;
//            }
//            /**
//             * 只有定时任务才会调用这个方法，然而调用这个方法会从数据库查询出方法进行重试
//             * 所以只要获取到执行失败的id，就可以进行批量修改状态和执行次数
//             */
//            if (!isComp) {
//                executeFailedIds = this.batchRecoupExecute(asyncExecDto, proxyMethodDto, asyncReq);
//            }else {
//                executeFailedIds = this.batchCompExecute(asyncExecDto, proxyMethodDto, asyncReq);
//            }
//        }
//        if (CollUtil.isEmpty(executeFailedIds)){
//            return;
//        }
//        // 批量修改
//        asyncReqService.batchUpdate(executeFailedIds, ExecStatusEnum.ERROR.getStatus());
//    }

//    private List<Long> batchCompExecute(AsyncExecDto asyncExecDto, ProxyMethodDto proxyMethodDto, AsyncReq asyncReq) {
//        List<Long> executeFailedIds = new ArrayList<>();
//        try {
//            this.invokeMethod(proxyMethodDto, asyncExecDto);
//        }catch (Exception e){
//            log.error("异步执行方法失败：{}", asyncExecDto, e);
//            String asyncType = asyncExecDto.getAsyncType();
//            if (AsyncTypeEnum.ASYNC.name().equals(asyncType) ||
//                    AsyncTypeEnum.THREAD.name().equals(asyncType)){
//                log.error("【{}】执行失败：{}", AsyncTypeEnum.getDesc(asyncExecDto.getAsyncType()), asyncExecDto);
//            }else {
//                executeFailedIds.add(asyncReq.getId());
//            }
//        }
//        return executeFailedIds;
//    }

//    private List<Long> batchRecoupExecute(AsyncExecDto asyncExecDto, ProxyMethodDto proxyMethodDto, AsyncReq asyncReq) {
//        List<Long> executeFailedIds = new ArrayList<>();
//        try {
//            this.invokeMethod(proxyMethodDto, asyncExecDto);
//            this.updateAsyncReq(asyncReq);
//        } catch (Exception e) {
//            if (asyncReq.getExecCount() + 1 > execCount) {
//                log.error("异步执行方法失败超过{}次：{}", execCount, asyncExecDto, e);
//            }
//            // 收集执行失败id
//            executeFailedIds.add(asyncReq.getId());
//            // todo 改成批量保存
//            this.saveAsyncLog(asyncReq, e);
//        }
//        return executeFailedIds;
//    }

}
